home *** CD-ROM | disk | FTP | other *** search
/ Software Vault: The Gold Collection / Software Vault - The Gold Collection (American Databankers) (1993).ISO / cdr52 / bcsmem.zip / ARRAY.DOC next >
Text File  |  1993-05-06  |  39KB  |  914 lines

  1. (10U
  2.  
  3.  
  4.                      Clipper Multi-dimensional Arrays
  5.  
  6.                             by Roger Donnay
  7.  
  8.                  A. Introduction
  9.                  B. The Evolution of the Multi-dimensional array
  10.                  C. Classes of Multi-dimensional arrays
  11.                     1. Parallel symmetrical arrays
  12.                     2. Ragged symmetrical arrays
  13.                     3. Asymmetrical arrays
  14.                  D. Working with Linked arrays
  15.                  E. Browsing a Multi-dimensional array
  16.                  F. Saving and Restoring Multi-dimensional arrays
  17.  
  18.  
  19.      ╔════════════════╗
  20.      ║  INTRODUCTION  ║
  21.      ╚════════════════╝
  22.  
  23.  
  24.         In my opinion, the single most powerful new feature of Clipper
  25.         5.0 is the multi-dimensional array system.  Sometimes referred
  26.         to as "ragged arrays", this incredibly versatile data type is
  27.         almost unlimited in capability and opens the door to a new
  28.         kind of programming just not possible in Summer 87.
  29.  
  30.         This seminar explores the use of ragged arrays from a practical
  31.         and theoretical viewpoint and is directed to the Clipper
  32.         programmer who is still discovering the power of Clipper 5.0.
  33.         I don't usually like to use a lot of code snippets in my
  34.         seminars, but teaching multi-dimensional arrays is simplified
  35.         by using lots of example code, so feel free to use the code
  36.         provided.  Much of the code examples are extracted from the
  37.         dCLIP libraries.
  38.  
  39.  
  40.      ╔═══════════════════════════════════════════════╗
  41.      ║  THE EVOLUTION OF THE MULTIDIMENSIONAL ARRAY  ║
  42.      ╚═══════════════════════════════════════════════╝
  43.  
  44.  
  45.         Clipper Summer 87 supported "single-dimensional" arrays.  The
  46.         data structure allowed the programmer to store information
  47.         in a structure called data "elements".  Most languages which
  48.         support arrays must first pre-define the length of the array
  49.         (number of elements) and the length and type of each element
  50.         of the array.  This is because memory must be pre-allocated
  51.         to allow for storage of data in the array elements.  This
  52.         memory allocation at compile/link time is a type of "early
  53.         binding" to insure that memory will always be available
  54.         during the running of the application.
  55.  
  56.         Clipper Summer 87 attempted to improve on this concept by
  57.         eliminating the need to pre-define the type and size of each
  58.         element of the array so the programmer could change any element
  59.         at will.  This new kind of "dynamic array" made programming
  60.         much easier and more powerful, but the programmer now needed
  61.         to consider the effects that this dynamic memory allocation
  62.         may have on the use of the memory pool at run-time.  Dynamic
  63.         arrays that were not properly managed could easily run the
  64.         program out of memory.  Fortunately, this did not occur often
  65.         in Summer 87 because even though any element of the array
  66.         could be accessed via one (1) symbol declaration (the array
  67.         name), the memory allocation and de-allocation for the elements
  68.         was handled on an individual basis.
  69.  
  70.         Example of a single-dimensional array (S87 code):
  71.  
  72.          * make an array of 5 elements
  73.          DECLARE myArray[5]
  74.          * initialize each element as a null string value
  75.          AFILL( myArray, "" )
  76.          * make element 2 a date value
  77.          myArray[2] = DATE()
  78.          * make element 4 a numeric value
  79.          myArray[4] = 234.56
  80.  
  81.          The array (myArray) will now look like this:
  82.  
  83.          myArray[1]  =  ""
  84.          myArray[2]  =  10/12/92
  85.          myArray[3]  =  ""
  86.          myArray[4]  =      234.56
  87.          myArray[5]  =  ""
  88.  
  89.        Probably the single most significant advantage to using arrays
  90.        in Summer 87 was that any element of an array could be used
  91.        in expressions exactly like any memory variable.  Another
  92.        significant advantage, but maybe not so obvious, was how using
  93.        arrays instead of memvars reduced the memory requirements of
  94.        the application.  This is because an entire array uses only
  95.        one symbol, whereas each memory variable uses a separate symbol.
  96.  
  97.        -- Why multi-dimensional arrays? --
  98.  
  99.        Arrays in Summer 87 made the programmer's life much simpler.
  100.        Scatter-gather routines, get lists, pick-lists, browse-screens,
  101.        menus, etc. became easier to program using arrays because the
  102.        size of the array and each element could be determined during
  103.        the actual running of the application.  Arrays, like databases,
  104.        are most effectively used and maintained when they are normalized.
  105.        "Normalizing" a database is simply a term describing the careful
  106.        structuring of a set of relational databases so that information
  107.        can be stored in related groups (ie, invoices, customers, etc.)
  108.        with little or no data redundancy.  Good programming practices
  109.        require that arrays also be structure in sets of related
  110.        information.  For example, creating an array-driven browse-system
  111.        would require the use of many arrays for storing information
  112.        like (1) screen-coordinates, (2) field-lists, (3) column-widths,
  113.        (4) data-buffers, (5) relations, etc..etc.   Summer 87 provided
  114.        the ability to create many different arrays, but no easy
  115.        mechanism to further relate these sets of arrays to each other.
  116.        In the above example, each array had to be given a separate name.
  117.        If you wished to save and restore a complete browse configuration,
  118.        the process was difficult and impractical and required that the
  119.        information be stored and retrieved from databases or kept alive
  120.        as PUBLIC arrays and symbols.
  121.  
  122.        I attempted, with a good degree of success, to eliminate the
  123.        separately defined arrays in my browse system and tie all the
  124.        groups of information together into a single array which was a
  125.        "psuedo" multi-dimensional array.  I found I could eliminate
  126.        the separate arrays and group everything together into one
  127.        array by maintaining a group of offset pointers into one long
  128.        array.  For example, elements 1-4 would be the screen coordinates,
  129.        5-55 the field names, 56-106 the column order, etc.  A sample of
  130.        code that accessed an element of this "psuedo" multi-dimensional
  131.        array would look like this:
  132.  
  133.         fieldname = aBrowse[ columns[ columnOffset + currentcol ] ] )
  134.  
  135.        A psuedo-multi-dimensional array would be treated like this:
  136.  
  137.        DECLARE myArray [ 4 + fcount()*2 ]
  138.        fieldoffset = 4
  139.        columnoffset = 4 + fieldoffset
  140.  
  141.        * put default screen coordinates into array
  142.        myArray [1] = 0
  143.        myArray [2] = 0
  144.        myArray [3] = 24
  145.        myArray [4] = 79
  146.  
  147.        * fill field name area of array
  148.        for i = 1 to fcount()
  149.          myArray[ fieldoffset + i ] = field(i)
  150.        next
  151.  
  152.        * fill column pointer area of array with default column widths
  153.        for i = 1 to fcount()
  154.          myArray[ columnoffset + i ] = len(transform(field(i),'@')
  155.        next
  156.  
  157.        Psuedo-multi-dimensional arrays in S87 were possible, but soon
  158.        the code started becoming difficult to manage with every line of
  159.        code making some reference to an array element "number" and
  160.        offset "number" rather than an easy-to-remember symbol "name".
  161.        Furthermore, it was impossible to dynamically re-size any
  162.        sub-array without completely re-building the entire array.
  163.        These limitations made it impractical to do multiple-window
  164.        browsing, dynamic adding and deleting of columns, one-to-many
  165.        browses, etc.
  166.  
  167.        Regardless of the limitations of the Summer 87 array system, it
  168.        was still so powerful that Clipper applications grew in size and
  169.        functionality at an alarming rate, thereby creating a whole new
  170.        set of problems.  The average Clipper programmer didn't want to
  171.        invest in a deeper understanding of the memory ramifications of
  172.        the heavy use of arrays, so he or she would work around the
  173.        problem by using third-party DOS memory managers and overlay
  174.        linkers that would give the application more DOS memory to start
  175.        with.  These third-party products were wonderful and extended
  176.        the lifetime of S87, but eventually these applications will
  177.        need to be upgraded to a language with a better memory manager.
  178.  
  179.  
  180.  
  181.        -- Clipper 5.0 arrays --
  182.  
  183.        Clipper 5.0 looks at arrays in an entirely new light.  To
  184.        overcome the difficulties that caused us to out-grow the S87
  185.        array system, the 5.0 development team needed to treat arrays
  186.        not as arrays in the traditional sense but as "linked lists".
  187.        The need to grow arrays, shrink arrays, pass parameters in
  188.        arrays, return arrays as values, and even store arrays and
  189.        code-blocks in other arrays required developing a system to
  190.        manage "pointers" and "pointers to pointers".  The ultimate
  191.        result of this architecture is a system that not only
  192.        optimizes the use of symbols, but also the use of memory for
  193.        the data elements.   This automatic system of "optimizing"
  194.        the data elements allows for creating incredibly large arrays
  195.        that can be passed around the application for use by many
  196.        routines and sub-routines by simply passing pointers rather
  197.        than data.  This concept vastly improves memory and speed
  198.        performance and revolutionizes the future of data-driven
  199.        programming.
  200.  
  201.        The development team took this concept even further and went as
  202.        far as managing the pointers to the sub-elements so as to simply
  203.        create a list of pointers to a "common" memory location for
  204.        elements that contained identical data.  For example, in Summer
  205.        87 or just about any other language, if you create an array of
  206.        1000 elements, and initialize each element to a length of 100
  207.        characters, a memory block of 100,000 (100*1000) bytes would be
  208.        required.  Clipper 5.0 effectively handles this task by creating
  209.        a system of logical pointers to the same area of memory until
  210.        the information in an element is modified.  This dynamic
  211.        optimization will cause even a huge array to allocate very
  212.        little memory when it is created, and from then on, only the
  213.        absolute minimum amount needed as the array information changes.
  214.  
  215.        This concept, by itself, is a significant advancement in array
  216.        architecture and memory management, but the development team
  217.        went even further and created a Virtual Memory Manager (VMM)
  218.        system that stores the array data in EMS memory.
  219.  
  220.        To see how effective this array optimization system really is,
  221.        try this little test:
  222.  
  223.          for i := 1 to 25
  224.            arrayname = 'array'+ALLTRIM(STR(i))
  225.            &arrayname = array(1000)
  226.            afill(&arrayname,space(1000))
  227.            ? arrayname, memory(0), memory(4)
  228.          next
  229.  
  230.        The above code will create 25 arrays of 1000 elements each and
  231.        and 100 characters in each element.  This is theoretically a
  232.        memory requirement of 25 megabytes.  In actuality the above
  233.        code when run under dCLIP uses 0 bytes of free pool memory
  234.        ( MEMORY(0) ) and only 340k of VMM ( MEMORY(4) ).
  235.  
  236.  
  237.      ╔══════════════════════════════════════╗
  238.      ║  CLASSES OF MULTIDIMENSIONAL ARRAYS  ║
  239.      ╚══════════════════════════════════════╝
  240.  
  241.         Clipper 5.0 provides a variety of methods to create and use
  242.         multi-dimensional arrays.  I am going to attempt to create
  243.         a classification for types of multi-dimensional arrays.  These
  244.         classes are provided only to help define the different types
  245.         of array programming and the advantages or disadvantages of
  246.         each.  Clipper does not make any distinction and treats all
  247.         arrays the same way.
  248.  
  249.         1.  Parallel symmetrical arrays.
  250.  
  251.             Parallel arrays are the most common arrays in Clipper
  252.             and are usually created using the ARRAY() function to
  253.             predefine the length of the array.  Parallel is a term
  254.             defining the way data elements in the array are "grouped".
  255.  
  256.             An example of parallel arrays would be an array of
  257.             3 sub-arrays in which each sub-array has the same number
  258.             of elements, and each element in each sub-array has a
  259.             direct relationship with the corresponding element in
  260.             every other sub-array.  For example, a parallel array
  261.             containing the structure of a database with 4 fields would
  262.             look like this:
  263.  
  264.                             Type  Contents
  265.  
  266.                aStru[1]       A   Sub-array of field names
  267.                  aStru[1,1]   C   CUST_NAME
  268.                  aStru[1,2]   C   SHIP_DATE
  269.                  aStru[1,3]   C   BALANCE
  270.                  aStru[1,4]   C   PRINT_FLAG
  271.                aStru[2]       A   Sub-array of field types
  272.                  aStru[2,1]   C   C
  273.                  aStru[2,2]   C   D
  274.                  aStru[2,3]   C   N
  275.                  aStru[2,4]   C   L
  276.                aStru[3]       A   Sub-array of field lengths
  277.                  aStru[3,1]   C   25
  278.                  aStru[3,2]   C   8
  279.                  aStru[3,3]   C   9
  280.                  aStru[3,4]   C   1
  281.                aStru[4]       A   Sub-array of field decimals
  282.                  aStru[4,1]   N   0
  283.                  aStru[4,2]   N   0
  284.                  aStru[4,3]   N   2
  285.                  aStru[4,4]   N   0
  286.  
  287.               This array could be created like this:
  288.  
  289.               aStru := Array( 4, Fcount() )
  290.               FOR i := 1 TO Fcount()
  291.                 aStru[1,i] := Field(i)
  292.                 aStru[2,i] := Type(Field(i))
  293.                 aStru[3,i] := Len(Transform(&(aStru[1,i]),'@'))
  294.                 aStru[4,i] := Eval( {|n| n := At('.',              ;
  295.                               Transform(&(astru[1,i]),'@')),       ;
  296.                               Iif( n > 0, astru[3,i] - n , 0 ) } )
  297.               NEXT
  298.  
  299.             Parallel arrays were very common in Summer 87 code, except
  300.             that each array was assigned a different name or symbol,
  301.             whereas parallel arrays in 5.0 can all exist under one
  302.             array name and are addressed by sub-element number.
  303.             Parallel arrays are most commonly used when you need a
  304.             pick-list of items (using ACHOICE() or similar function)
  305.             and then need to index into a set of parallel arrays to
  306.             extract all related information based on a pointer returned
  307.             by picking an item from the first parallel array.
  308.  
  309.  
  310.         2.  Ragged symmetrical arrays.
  311.  
  312.             Ragged symmetrical arrays are a new concept introduced in
  313.             Clipper 5.0 and are structured in such a way as to make the
  314.             data in the array elements much easier to evaluate in a
  315.             single expression with the AEVAL() function.
  316.  
  317.             An example of ragged symmetrical arrays would be an array
  318.             of 4 sub-arrays in which each sub-array has the same number
  319.             of elements (i.e. the same "structure") however, there is
  320.             no direct relationship between the information in any one
  321.             sub-array and any other sub-array.  Instead, all the
  322.             pertinent related information is contained in all the
  323.             elements of a single sub-array.  Let's create a ragged
  324.             symmetrical array containing the structure of the same
  325.             database we used in the parallel array example above.  The
  326.             array would look like this:
  327.  
  328.                             Type  Contents
  329.  
  330.                aStru[1]       A   Field 1 Information
  331.                  aStru[1,1]   C   CUST_NAME
  332.                  aStru[1,2]   C   C
  333.                  aStru[1,3]   C   25
  334.                  aStru[1,4]   N   0
  335.                aStru[2]       A   Field 2 Information
  336.                  aStru[2,1]   C   SHIP_DATE
  337.                  aStru[2,2]   C   D
  338.                  aStru[2,3]   C   8
  339.                  aStru[2,4]   N   0
  340.                aStru[3]       A   Field 3 Information
  341.                  aStru[3,1]   C   BALANCE
  342.                  aStru[3,2]   C   N
  343.                  aStru[3,3]   C   9
  344.                  aStru[3,4]   N   2
  345.                aStru[4]       A   Field 4 Information
  346.                  aStru[4,1]   C   PRINT_FLAG
  347.                  aStru[4,2]   C   L
  348.                  aStru[4,3]   C   1
  349.                  aStru[4, ]   N   0
  350.  
  351.               This array could be created like this:
  352.  
  353.               aStru := {}
  354.               FOR i := 1 TO Fcount()
  355.                 Aadd( aStru, Array(4) )
  356.                 aStru[i,1] := Field(i)
  357.                 aStru[i,2] := Type(Field(i))
  358.                 aStru[i,3] := Len(Transform(&(aStru[i,1]),'@'))
  359.                 aStru[i,4] := Eval( {|n| n := At('.',              ;
  360.                               Transform(&(astru[i,1]),'@')),       ;
  361.                               Iif( n > 0, astru[i,3] - n , 0 ) } )
  362.               NEXT
  363.  
  364.             The main advantage of organizing your arrays in ragged
  365.             symmetrical form is in the ease of evaluating the
  366.             information in the array.  For example, listing the
  367.             structure of a database that has been loaded into a ragged
  368.             symmetrical array would require only a single expression:
  369.  
  370.               Aeval( aStru, { |a| qout(pad(a[1],a[2],a[3],a[4]) } )
  371.  
  372.             Whereas trying to list the same information from parallel
  373.             arrays would require more complex structural code:
  374.  
  375.              FOR i := 1 TO Fcount()
  376.                qout(pad(aStru[1,i],10),aStru[2,i],aStru[3,i],aStru[4,i])
  377.              NEXT
  378.  
  379.             The disadvantage, however of organizing data in ragged
  380.             symmetrical form is that the array cannot be simply passed
  381.             to a pick-list function such as ACHOICE(), so here's a
  382.             handy function to convert one array type to another.
  383.  
  384.             FUNCTION dc_aconvert ( aInput )
  385.             LOCAL i, j, aOutput := {}
  386.             aOutput := Array( Len( aInput[1] ), Len( aInput ) )
  387.             FOR i := 1 TO LEN( aInput )
  388.               FOR j := 1 TO LEN( aInput[1] )
  389.                 aOutput[j,i] := aInput[i,j]
  390.               NEXT
  391.             NEXT
  392.             RETURN aOutput
  393.  
  394.             You would use this function as follows:
  395.  
  396.             aRaggedDir := Directory()
  397.             aParallelDir := DC_ACONVERT( aRaggedDir )
  398.             nChoice := Achoice( 10,10,20,40, aParallelDir[1] )
  399.             ? 'The size of '+aParallelDir[1,nChoice]+' is '+    ;
  400.               Str( aParallelDir[2,nChoice] )
  401.  
  402.         3.  Asymmetrical arrays.
  403.  
  404.             An asymmetrical array is simply an array of information
  405.             in which no element of the array has a "direct" indexed or
  406.             ordinal relationship with any other element of the array
  407.             yet all the information in all the elements make up a
  408.             complete package of information.  Basically, asymmetrical
  409.             arrays can be used in place of memvars, in which each
  410.             element of the array represents a specific piece of
  411.             information.  So why not use memvars instead of arrays?
  412.             Mainly, because memvars cannot be easily grouped together
  413.             into a complete "set" of information that can be easily
  414.             stored and retrieved, whereas and entire array can be
  415.             saved, restored, or even passed as a single parameter.
  416.  
  417.             An example of an asymmetrical array would be an array
  418.             in which element 1 is another array which always
  419.             represents the screen coordinates of a browse screen,
  420.             element 2 is a character field with color of the screen,
  421.             element 3 is a numeric field representing the work area,
  422.             element 4 is a logical field, element 5 is a date field,
  423.             etc., etc.  In fact an asymmetrical array may even contain
  424.             sub-arrays that are ragged symmetrical or parallel
  425.             symmetrical.  This entire multi-dimensional array could
  426.             contain all the information necessary to repaint all
  427.             browse screens for all work areas.  This kind of array
  428.             is effectively an "object", because it encapsulates all
  429.             the necessary information about a program configuration
  430.             into a nice, neat package.
  431.  
  432.             The advantage of working with large asymmetrical arrays
  433.             is that only one symbol is used for the entire array,
  434.             making it easy to save, restore and pass around an
  435.             application.  The disadvantage, is that the programmer
  436.             must remember which element of which sub-array contains
  437.             the desired data thereby making the source code very
  438.             cryptic, unreadable, and difficult to maintain.  In
  439.             addition, if it became necessary to add new elements to
  440.             the array, the ordinal position of each other element may
  441.             change.  This is where the pre-processor is not only very
  442.             handy but an absolute necessity when working with large
  443.             asymmetrical arrays.  Assigning a "manifest constant" to
  444.             an array element now allows the programmer to use multi-
  445.             dimensional array elements and sub-elements in the same
  446.             way he/she uses memory variables.  It isn't necessary to
  447.             remember which sub-element of an array contains a piece
  448.             of information when a #define statement can be used to
  449.             create any name desired.  From then on, the programmer
  450.             can write or read array information via an easy-to-remember
  451.             set of variable names.
  452.  
  453.             There are several schools of thought when #defining
  454.             manifest constants to represents array elements.  Many
  455.             programmers prefer to use the #define "only" for defining
  456.             numeric values.  Here is an example of this kind of array
  457.             pre-processing:
  458.  
  459.               // BROWSE sub-array definitions
  460.               #define MAIN          1
  461.               #define COORDINATES   2
  462.               #define FIELDS        4
  463.  
  464.               // COORDINATES definitions
  465.               #define STARTROW      1
  466.               #define STARTCOL      2
  467.               #define ENDROW        3
  468.               #define ENDCOL        4
  469.  
  470.             Defining array manifests in this manner requires that you
  471.             use more than 1 variable name to access sub-array
  472.             information.  For example you would get the value of the
  473.             start row of the display like this:
  474.  
  475.               nStartRow := BROWSE[ nWorkArea, COORDINATES, STARTROW ]
  476.  
  477.             Another method of #defining array pointers is by using one
  478.             a "manifest symbol" rather than a "manifest constant".
  479.             Here is an example of manifest symbols:
  480.  
  481.               // BROWSE sub-array definitions
  482.               #define aMAIN          1
  483.               #define aCOORDINATES   2
  484.               #define aFIELDS        4
  485.  
  486.               // COORDINATES definitions
  487.               #define nSTART_ROW      BROWSE[ nWorkarea, 2, 1 ]
  488.               #define nSTART_COLUMN   BROWSE[ nWorkarea, 2, 2 ]
  489.               #define nEND_ROW        BROWSE[ nWorkarea, 2, 3 ]
  490.               #define nEND_COLUMN     BROWSE[ nWorkarea, 2, 4 ]
  491.  
  492.             Defining array manifests in this manner allows you to
  493.             access multi-dimensional array information via shorter
  494.             and simpler commands.  For example you would get the value
  495.             of the start row of the display like this:
  496.  
  497.               nStartRow := nSTART_ROW
  498.  
  499.             There currently is no "Hungarian notation" standard for
  500.             manifest constants so you can use any name you wish, but
  501.             I prefer the following format:
  502.  
  503.               xYYYYYYYYYY
  504.               - --------
  505.               |    |
  506.               |    -------- The assigned name (in all capitals)
  507.               ------------- The "type" of the data (in lower case)
  508.                         (a-Array, n-Numeric, d-Date, c-Character, etc)
  509.  
  510.             Defining manifest contstants in this manner makes them stand
  511.             out in your program and not get confused with your memory
  512.             variables or database field names.
  513.  
  514.  
  515.      ╔══════════════════════════════╗
  516.      ║  WORKING WITH LINKED ARRAYS  ║
  517.      ╚══════════════════════════════╝
  518.  
  519.        Although array elements in Clipper 5.0 can store the same types
  520.        of data as memvars, it must be clearly understood that the 5.0
  521.        linked array system creates new "pointers", NOT new "values".
  522.        If this truth is forgotten or misunderstood you may not get the
  523.        expected results in your program.  Let's take the following
  524.        code as an example:
  525.  
  526.        n1 := 1
  527.        n2 := n1
  528.        n2 := 2
  529.        ? n1
  530.        1
  531.  
  532.        a1 := { 1, 2, 3 }
  533.        a2 := a1
  534.        a2[1] := 10
  535.        ? a1[1]
  536.        10
  537.  
  538.        In the numeric memvar example above, n2 := n1 assigns the "value"
  539.        of n1 to n2.  There is no other relationship between n1 and n2.
  540.        The value of n2 can now be changed to anything without affecting
  541.        the value of n1.
  542.  
  543.        In the array example above, a2 := a1 assigns the "address" of
  544.        a1 to a2.  This now creates a direct relationship between a1 and
  545.        a2 by creating a new set of pointers to the same information.
  546.        Now if any of the elements of a2 are changed, the information in
  547.        a1 will also be changed.
  548.  
  549.        If it desired to copy the information from an array to a new
  550.        array and then work with the new array without affecting the
  551.        original array, use the ACLONE() function.
  552.  
  553.        Actually, this "linked-list" type of array system is not a
  554.        disadvantage, but in the contrary, is the reason why arrays in
  555.        Clipper 5.0 are so powerful.  When an array is passed as a
  556.        parameter to another function or returned as a value from a
  557.        function, only the pointer to the same information in memory
  558.        is actually passed, not a set of values.  This means that
  559.        if the data in a LOCAL array that has been passed to a function
  560.        is modified, the original passed array is also modified.
  561.  
  562.        Look at the following code:
  563.  
  564.          PROCEDURE  TEST
  565.          LOCAL a1 := {1,2,3}
  566.          LOCAL a2
  567.          a2 := TEST2 ( a1 )
  568.          ? a1[1]
  569.          10
  570.  
  571.          STATIC FUNCTION TEST2 ( a3 )
  572.          LOCAL a4 := a3
  573.          a4[1] := 10
  574.          a4[2] := 11
  575.          a4[3] := 12
  576.          RETURN a4
  577.  
  578.        In the above example, even though a4 in function TEST2 has been
  579.        designated as a LOCAL memvar, the assigment of a3 to a4 will
  580.        force the information in a1 of procedure TEST to change when
  581.        it is changed in function TEST2.
  582.  
  583.  
  584.      ╔═════════════════════════════════════╗
  585.      ║  BROWSING A MULTIDIMENSIONAL ARRAY  ║
  586.      ╚═════════════════════════════════════╝
  587.  
  588.        I have seen lots of array browsers that don't give me the
  589.        dimensional view of the array that I like.  Most array browsers
  590.        are written using Tbrowse, this one is not.  This browser not
  591.        only views the information in an array or object but also allows
  592.        you to change the value and type of any array element or sub-
  593.        element, or to grow or shrink any sub-array.  Browsing an array
  594.        in this manner requires building a different image of the
  595.        array using macros and a Private memvar.  This may not be a
  596.        desirable programming practice to some, but it yields some good
  597.        results.  Note: This array browser can take a few seconds to
  598.        build the Achoice image of the array if it is a large array.
  599.  
  600.        STATIC nLastKey := 0
  601.        MEMVAR aNewArray
  602.  
  603.        FUNCTION abrowse ( aArray )
  604.  
  605.        LOCAL aArrayView, nSelect, nStart, cSaveScreen, cSaveScrn2,;
  606.        cArrayElem, xValue, cType, nValueLoc, nRow, cElement, nLength,;
  607.        cValue, cOldType, nOldLength, cSubName, nElement, lReBuild,;
  608.        nLastElem, getlist := {}
  609.  
  610.        IF !(VALTYPE(aArray)$'AO')
  611.          RETURN .f.
  612.        ENDIF
  613.        PRIVATE aNewArray := aArray
  614.        aArrayView := ABROWSE3( {}, aNewArray, '', 0 )
  615.        @ 0,2 TO 24,79 DOUBLE
  616.        @ 24,7 SAY "┤ ENTER = Edit,  ESCape = Exit,  + = Add,  "+;
  617.                   "DEL = Delete, INS = Insert ├"
  618.        nSelect := 1
  619.        nStart := 1
  620.        DO WHILE .t.
  621.          SET KEY 7 TO ABROWSE4
  622.          SET KEY 22 TO ABROWSE4
  623.          SET KEY 43 TO ABROWSE4
  624.           nLastKey := 0
  625.           nSelect := ACHOICE(1,3,23,78,aArrayView,,,nSelect,nStart)
  626.          SET KEY 7 TO
  627.          SET KEY 22 TO
  628.          SET KEY 43 TO
  629.          nStart := ROW()-1
  630.          IF LASTKEY()=27
  631.            EXIT
  632.          ENDIF
  633.          IF nSelect = 0
  634.            LOOP
  635.          ENDIF
  636.          cArrayElem := aArrayView[nSelect]
  637.          cType :=  SUBSTR( cArrayElem, AT( '],', cArrayElem)+2, 1 )
  638.          nValueLoc := AT( '],', cArrayElem ) + 4
  639.          cValue :=  SUBSTR( cArrayElem, nValueLoc )
  640.          cElement := SUBSTR( cArrayElem, 1, nValueLoc-4 )
  641.          nRow := IIF( ROW()>11, 2, 12 )
  642.          cArrayElem := 'aNewArray' + cElement
  643.          nLength := LEN(IIF(cType#'A',cValue,&cArrayElem))
  644.          lReBuild := .f.
  645.          FOR nLastElem := LEN(cElement) TO 1 STEP -1
  646.            IF SUBSTR(cElement,nLastElem,1) $ '[,'
  647.              EXIT
  648.            ENDIF
  649.          NEXT
  650.          cSubName := 'aNewArray' + ALLTRIM(SUBSTR(cElement,1,nLastElem-1))+;
  651.                      IIF(SUBSTR(cElement,nLastElem,1)=',',']','')
  652.          nElement := VAL(STRTRAN(SUBSTR(cElement,nLastElem+1),']',''))
  653.          nOldLength := nLength
  654.  
  655.          DO CASE
  656.  
  657.            CASE nLastKey = 22   // Insert key inserts an element
  658.              AINS( &cSubName, nElement)
  659.              lReBuild := .t.
  660.  
  661.            CASE nLastKey = 7    // Delete key deletes an element
  662.              ADEL( &cSubName, nElement)
  663.              lReBuild := .t.
  664.  
  665.            CASE nLastKey = 43   // + key adds a new element
  666.              AADD( &cSubName, nil )
  667.              lReBuild := .t.
  668.  
  669.            CASE LASTKEY()=13
  670.              cSaveScrn2 := SaveScreen( nRow,5,nRow+4,75 )
  671.              @ nRow,5 CLEAR TO nRow+4,75
  672.              @ nRow,5 TO nRow+4,75
  673.              cOldType := cType
  674.              @ nRow+1,7 SAY '  Type' GET cType PICT '!' VALID cType$'ACDNLU'
  675.              READ
  676.              DO CASE
  677.                CASE cType = 'L'
  678.                  xValue := 'T'$cValue
  679.                CASE cType = 'D'
  680.                  xValue := CTOD(cValue)
  681.                CASE cType = 'N'
  682.                  xValue := VAL(cValue)
  683.                CASE cType = 'U'
  684.                  xValue := nil
  685.                CASE cType $ 'CA'
  686.                  @ nRow+2,8 SAY 'Length' GET nLength
  687.                  READ
  688.                  IF cType = 'C'
  689.                    xValue := PAD(cValue,nLength)
  690.                  ENDIF
  691.              ENDCASE
  692.              DO CASE
  693.                CASE cType = 'N'
  694.                  @ nRow+3,8 SAY ' Value' GET xValue PICT ;
  695.                    '9999999999999.9999999'
  696.                  READ
  697.                CASE !cType $ 'UA'
  698.                  @ nRow+3,8 SAY ' Value' GET xValue PICT '@S58'
  699.                  READ
  700.              ENDCASE
  701.              IF LASTKEY()#27
  702.                IF cType#'A'
  703.                  IF cType='U'
  704.                    aArrayView[nSelect] := cElement + ','+cType+','
  705.                  ELSE
  706.                    aArrayView[nSelect] := cElement + ','+cType+','
  707.                    +TRANSFORM(xValue,'@A')
  708.                  ENDIF
  709.                  &cArrayElem := xValue
  710.                ELSE
  711.                  IF TYPE(cArrayElem)#'A'
  712.                    &cArrayElem := {}
  713.                  ENDIF
  714.                  ASIZE(&cArrayElem,nLength)
  715.                ENDIF
  716.                lReBuild := (cOldType # cType .AND. ;
  717.                   ( cType='A' .OR. cOldType='A' )) ;
  718.                     .OR. (cType='A' .AND. cOldType='A' ;
  719.                     .AND. nLength # nOldLength)
  720.              ENDIF
  721.              RestScreen(nRow,5,nRow+4,75,cSaveScrn2)
  722.          ENDCASE
  723.          IF lReBuild
  724.             aArrayView := ABROWSE3( {}, aNewArray, '', 0 )
  725.          ENDIF
  726.        ENDDO
  727.        DC_IMPL(cSaveScreen)
  728.        RETURN .t.
  729.  
  730.        // ----------------------------------------------------------- //
  731.  
  732.        STATIC FUNC aBrowse3 ( aArrayView, aArrayDisp, cElement, nRecurs )
  733.  
  734.        LOCAL  nElement, cTextLine, cType, nArrayLen, cValue, cElement2
  735.  
  736.        nArrayLen := LEN(aArrayDisp)
  737.        FOR nElement := 1 TO nArrayLen
  738.            cType := VALTYPE(aArrayDisp[nElement])
  739.            cTextLine := ''
  740.            cElement2 := cElement+'['+ALLTRIM(STR(nElement,4))+']'
  741.            cElement2 := STRTRAN( cElement2, '][', ',' )
  742.            DO CASE
  743.              CASE cType='C'
  744.                cTextLine := aArrayDisp[nElement]
  745.              CASE cType='N'
  746.                cTextLine := STR(aArrayDisp[nElement])
  747.              CASE cType='D'
  748.                cTextLine := DTOC(aArrayDisp[nElement])
  749.              CASE cType='L'
  750.                cTextLine := IIF(aArrayDisp[nElement],'T','F')
  751.              CASE cType$'AO'
  752.                AADD( aArrayView, SPACE(nRecurs)+cElement2+','+cType+','+;
  753.                  cTextLine )
  754.                aArrayView := ABROWSE3( aArrayView, aArrayDisp[nElement], ;
  755.                                           cElement2, nRecurs+1 )
  756.                LOOP
  757.            ENDCASE
  758.            AADD( aArrayView, SPACE(nRecurs)+cElement2+','+cType+','+;
  759.                  cTextLine )
  760.        NEXT
  761.        RETURN aArrayView
  762.  
  763.        // ---------------------------------------------------------- //
  764.  
  765.        STATIC PROC ABROWSE4
  766.  
  767.        nLastKey := LASTKEY()
  768.        KEYBOARD CHR(13)
  769.        RETURN
  770.  
  771.        // ---------------------------------------------------------- //
  772.  
  773.  
  774.      ╔═══════════════════════════════╗
  775.      ║  SAVING AND RESTORING ARRAYS  ║
  776.      ╚═══════════════════════════════╝
  777.  
  778.        Saving and restoring arrays to and from a disk file can offer
  779.        the advantage of creating "persitent" objects.  The following
  780.        code will save and restore complete multi-dimensional arrays
  781.        that DO NOT contain code blocks.  Currently, there is no
  782.        mechanism provided within Clipper to save/restore code blocks
  783.        to disk, only to memvars.  If you are using code blocks in
  784.        arrays, you must store the code block as a string and macro-
  785.        compile the string to restore the code block.  The code
  786.        provided here is 100% clipper code for compatability purposes
  787.        only.  The array saving/restoring mechanism I prefer is identical
  788.        to the below code, except that the F_EOF and F_READLINE functions
  789.        are replaced with assembly-level functions for speed.
  790.  
  791.  
  792.        MEMVAR aOutArray
  793.  
  794.        FUNCTION arrayread ( cFileName )
  795.  
  796.        LOCAL  cTextLine, cType, cElement, nArrayLen,  nHandle,;
  797.               nComma, cValue, cArray
  798.        PRIVATE aOutArray := {}
  799.  
  800.        nHandle := FOPEN( cFileName )
  801.        IF nHandle<=0
  802.          RETURN nil
  803.        ENDIF
  804.        nArrayLen := 0
  805.        DO WHILE !F_EOF(nHandle)
  806.          cTextLine := F_READLINE(nHandle)
  807.          IF LEFT(cTextLine,1) = '[' .OR. LEFT(cTextLine,1) = ','
  808.            nComma := AT(',',cTextLine)
  809.            cElement := TRIM(SUBSTR(cTextLine,1,nComma-1))
  810.            cType := UPPER(SUBSTR(cTextLine,nComma+1,1))
  811.            cValue := SUBSTR(cTextLine,nComma+3)
  812.            cArray := 'aOutArray'+cElement
  813.          ELSE
  814.            cValue := cValue + cTextLine
  815.          ENDIF
  816.          DO CASE
  817.            CASE cType='C'
  818.              &cArray := STRTRAN(cValue,CHR(255),CHR(13)+CHR(10))
  819.            CASE cType='N'
  820.              &cArray := VAL(cValue)
  821.            CASE cType='D'
  822.              &cArray := CTOD(cValue)
  823.            CASE cType='L'
  824.              &cArray := IIF(cValue='Y',.t.,.f.)
  825.            CASE cType='A'
  826.              cArray := 'aOutArray'+cElement
  827.              &cArray := {}
  828.              ASIZE(&cArray,VAL(cValue))
  829.            CASE cType='B'
  830.              &cArray := &('"'+cValue+'"')
  831.          ENDCASE
  832.        ENDDO
  833.        FCLOSE(nHandle)
  834.        RETURN aOutArray
  835.  
  836.        // ------------------------------------------------------------ //
  837.  
  838.        FUNCTION arraywrite ( aArray, cFileName )
  839.  
  840.        LOCAL  cTextLine, cType, nElement, nHandle
  841.  
  842.        IF VALTYPE(aArray)#'A'
  843.          RETURN .F.
  844.        ENDIF
  845.        nHandle := FCREATE( cFileName )
  846.        IF nHandle<=0
  847.          RETURN .f.
  848.        ENDIF
  849.        _DCARRAY_W2 ( aArray, nHandle, '' )
  850.        FCLOSE(nHandle)
  851.        RETURN .T.
  852.  
  853.        // ------------------------------------------------------------- //
  854.  
  855.        STATIC FUNCTION _dcarray_w2 ( aArray , nHandle, cElement )
  856.  
  857.        LOCAL  nElement, cTextLine, cType, nArrayLen, cValue
  858.  
  859.        nArrayLen := LEN(aArray)
  860.        FWRITE(nHandle,cElement+',A,'+ALLTRIM(STR(nArrayLen))+;
  861.               CHR(13)+CHR(10))
  862.        FOR nElement := 1 TO nArrayLen
  863.            IF VALTYPE(aArray[nElement])='U'
  864.              LOOP
  865.            ENDIF
  866.            cValue := aArray[nElement]
  867.            cType := VALTYPE(cValue)
  868.            cTextLine := ''
  869.            DO CASE
  870.              CASE cType='C'
  871.                cTextLine := STRTRAN(HARDCR(cValue),CHR(13)+;
  872.                             CHR(10),CHR(255))
  873.              CASE cType='N'
  874.                cTextLine := ALLTRIM(STR(cValue))
  875.              CASE cType='D'
  876.                cTextLine := DTOC(cValue)
  877.              CASE cType='L'
  878.                cTextLine := IIF(cValue,'Y','N')
  879.              CASE cType='A'
  880.                _DCARRAY_W2 ( aArray[nElement], nHandle, ;
  881.                        cElement+'['+ALLTRIM(STR(nElement,4))+']')
  882.                LOOP
  883.            ENDCASE
  884.            FWRITE(nHandle,cElement+'['+ALLTRIM(STR(nElement,4))+']';
  885.                   +','+cType+','+cTextLine+CHR(13)+CHR(10))
  886.        NEXT
  887.        RETURN .T.
  888.  
  889.        // -------------------------------------------------------- //
  890.  
  891.        STATIC FUNCTION f_eof ( nHandle )
  892.        LOCAL   nCurrent, lEof
  893.        nCurrent := FSEEK(nHandle,0,1)
  894.        lEof := .f.
  895.        IF nCurrent >= FSEEK(nHandle,0,2)
  896.          lEof := .t.
  897.        ENDIF
  898.        FSEEK(nHandle,nCurrent,0)
  899.        RETURN lEof
  900.  
  901.        // --------------------------------------------------------- //
  902.  
  903.        STATIC FUNCTION f_readline ( nHandle )
  904.  
  905.        LOCAL  cString, nCurrent, nCr
  906.        nCurrent  :=  FSEEK(nHandle,0,1)
  907.        cString  :=  FREADSTR(nHandle,500)
  908.        nCr  :=  AT (CHR(13),cString)
  909.        FSEEK(nHandle,nCurrent,0)
  910.        FSEEK(nHandle,nCr+1,1)
  911.        RETURN SUBSTR(cString,1,nCr-1)
  912.  
  913.  
  914.